//    Copyright (C) Kherty.  All rights reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using OpenLS.Spreadsheet.Extensibility;
using OpenLS.Spreadsheet.StandardAddIn;
using OpenLS.Spreadsheet.StandardAddIn.Statistics;

namespace OpenLS.Spreadsheet.AnalysisToolPak
{
    [AddInFunctions]
    [AllowDoubleToEnumConversion]
    [AllowStringToDateTimeConversion]
    //\\[AccessCellValue]
    public static class FinancialFunctions
    {

        public static double EuroConvert(double Number, string Source, string Target, [Optional(false)]bool Full_Precision, [Optional(int.MinValue)] int Triangulation_precision)
        {
            return EuroHelper.EuroConvert(Number, Source, Target, Full_Precision, Triangulation_precision);
        }
        public static double TBillPrice(DateTime settlement, DateTime maturity, double discount)
        {
            double res, dsm;
            dsm = (maturity - settlement).TotalDays;
            if (settlement > maturity || discount < 0 || dsm > 365)
                throw new ErrorException(Errors.NumError);
            res = 100*(1.0 - (discount*dsm)/360.0);
            return (res);
        }
        public static double TBilleQ(DateTime settlement, DateTime maturity, double discount)
        {
            double dsm = (maturity - settlement).TotalDays;
            if (settlement > maturity || discount < 0 || dsm > 365)
                throw new ErrorException(Errors.NumError);
            double divisor = 360 - discount*dsm;
            if (divisor == 0)
                throw new ErrorException(Errors.DivError);
            return ((365*discount)/divisor);
        }

        public static double TBillYield(DateTime settlement, DateTime maturity, double pr)
        {
            double res, dsm;

            dsm = (maturity - settlement).TotalDays;
            if (pr <= 0 || dsm <= 0 || dsm > 365)
                throw new ErrorException(Errors.NumError);
            res = (100.0 - pr) / pr * (360.0 / dsm);
            return (res);
        }




        public static object AccrInt(DateTime issue, DateTime first_interest, DateTime settlement,
                                     double rate, double par, Frequency freq, [Optional(0)] int basis,
                                     [Optional(true)] bool calcMethod)
        {
            if (rate <= 0.0 || par <= 0.0 || !DateBasis.IsValidBasis(basis))
                return Errors.NumError;
            if (issue >= settlement)
                return Errors.NumError;
            switch (freq)
            {
                case Frequency.Annual:
                case Frequency.Quarterly:
                case Frequency.SemiAnnual:
                    break;
                default:
                    return Errors.NumError;
            }
            DateBasis b = DateBasis.GetBasis(basis);

            double a = b.DayDifference(settlement, issue);
            double d = b.DaysInYear(settlement);
            if (a < 0 || d <= 0)
                return Errors.NumError;
            if (settlement > first_interest)
            {
                if (!calcMethod)
                    return par*rate*b.YearDifference(settlement, first_interest);
            }
            return par*rate*b.YearDifference(settlement, issue);
        }

        public static object AccrIntM(DateTime issue, DateTime settlement, double rate, double par,
                                      [Optional(0)] int basis)
        {
            double a, d;
            if (!DateBasis.IsValidBasis(basis))
                return Errors.NumError;
            if (issue > settlement)
                return Errors.NumError;
            DateBasis b = DateBasis.GetBasis(basis);
            a = b.DayDifference(settlement, issue);
            d = b.DaysInYear(issue);

            if (a < 0 || d <= 0 || par <= 0 || rate <= 0)
                return Errors.NumError;

            return (par*rate*a/d);
        }

        public static object AMORDEGRC(double cost, DateTime Date_purchased, DateTime First_period, double Salvage,
                                       int Period, double Rate, [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (Rate <= 0)
                return Errors.NumError;
            double usePeriod = 1d/Rate;
            double coefficient;
            if (usePeriod < 3)
                coefficient = 1.0;
            else if (usePeriod < 5)
                coefficient = 1.5;
            else coefficient = usePeriod <= 6 ? 2.0 : 2.5;

            Rate *= coefficient;
            double result = Math.Round(DateFunctions.YearFrac(Date_purchased, First_period, basis)*Rate*cost);
            cost -= result;
            double rest = cost - Salvage;
            for (int n = 0; n < Period; n++)
            {
                result = Math.Round(Rate*cost);
                rest -= result;
                if (rest < 0.0)
                {
                    switch (Period - n)
                    {
                        case 0:
                        case 1:
                            return (Math.Round(cost*0.5));
                        default:
                            return 0d;
                    }
                }
                cost -= result;
            }
            return result;
        }

        public static object AMORLINC(double cost, DateTime Date_purchased, DateTime First_period, double Salvage,
                                      int Period, double Rate, [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            Contract.Ensures(Contract.Result<object>() is Error || Contract.Result<object>() is double);
            if (Rate < 0)
                return Errors.NumError;

            double r = cost*Rate;
            double fr = DateFunctions.YearFrac(Date_purchased, First_period, basis)*r;
            var numPeriods = (int) (((cost - Salvage) - fr)/r);

            if (Period == 0)
                return fr;
            if (Period <= numPeriods)
                return r;
            if (Period == numPeriods + 1)
                return ((cost - Salvage) - r*numPeriods - fr);
            return 0d;
        }

        public static double CoupDayBS(DateTime settlement, DateTime maturity, Frequency frequency,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            DateTime previousCoup = ActualActualBasis.CoupCd(settlement, maturity, frequency, true, false); //\\sure?
            return -b.DayDifference(previousCoup, settlement);
        }

        public static double CoupDays(DateTime settlement, DateTime maturity, Frequency frequency,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis((int) basis);
            return b.CoupDays(settlement, maturity, (frequency));
        }

        public static double CoupDaysNC(DateTime settlement, DateTime maturity, Frequency frequency,
                                        [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            if (maturity < settlement)
                throw new ErrorException(Errors.NumError);
            DateTime couppcd = CoupPCDImp(settlement, maturity, frequency, basis);
            DateTime coupncd = CoupNCDImp(couppcd,  maturity, frequency, basis);
            double result;
            result = (coupncd - settlement).TotalDays;
            //result =  b.DayDifference(coupncd, settlement);
            //result = b.DaysBetween(settlement, coupncd);
            Debug.WriteLine(couppcd + " " + coupncd + " " + result);

            return result;
        }

        /// <summary>
        /// Returns a number that represents the next coupon date after the settlement date.
        /// </summary>
        /// <param name="settlement"></param>
        /// <param name="maturity"></param>
        /// <param name="frequency"></param>
        /// <param name="basis"></param>
        /// <returns></returns>
        public static double CoupNCD([WorksheetContext]IWorksheet worksheet, DateTime settlement, DateTime maturity, Frequency frequency,
                                     [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            var result =  ActualActualBasis.CoupCd(settlement, maturity, frequency, true, true);
            return worksheet.Workbook.DateTimeToDouble(result);
        }
        /// <summary>
        /// Returns a number that represents the next coupon date after the settlement date.
        /// </summary>
        /// <param name="settlement"></param>
        /// <param name="maturity"></param>
        /// <param name="frequency"></param>
        /// <param name="basis"></param>
        /// <returns></returns>
        static DateTime CoupNCDImp(DateTime settlement, DateTime maturity, Frequency frequency,
                                     [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            var result = ActualActualBasis.CoupCd(settlement, maturity, frequency, true, true);
            return result;
        }


        public static int COUPNUM(DateTime settlement, DateTime maturity, Frequency frequency,
                                     [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateTime couponDate = maturity;

            int months = 12*(maturity.Year - settlement.Year) + maturity.Month - settlement.Month;
            couponDate = couponDate.AddMonths(months);

            const bool eom = true;
            if (eom && maturity.Day == DateTime.DaysInMonth(maturity.Year, maturity.Month))
                couponDate =
                    new DateTime(couponDate.Year, couponDate.Month,
                                 DateTime.DaysInMonth(couponDate.Year, couponDate.Month));

            if (settlement.Day >= couponDate.Day)
                months--;
            return (1 + months/(12/(int) frequency));
        }

        public static double CoupPCD([WorksheetContext]IWorksheet worksheet, DateTime settlement, DateTime maturity, Frequency frequency,
                                     [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            var dateTime = CoupPCDImp(settlement, maturity, frequency, basis);
            return worksheet.Workbook.DateTimeToDouble(dateTime);
            
        }

        static DateTime CoupPCDImp(DateTime settlement, DateTime maturity, Frequency frequency,
                                     [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            DateTime dt = ActualActualBasis.CoupCd(settlement, maturity, frequency, true, false);
            return dt;
        }

        public static object CumIPmt(double rate, double nper, double pv, int start_period, int end_period,
                                     PaymentType type)
        {
            if (start_period < 1 || end_period < start_period || rate <= 0 || end_period > nper || nper <= 0
                || pv <= 0)
                return Errors.NumError;

            double result = 0d;
            double payment = Pmt(rate, nper, pv, 0.0, type);

            if (start_period == 1)
            {
                if (type <= 0)
                {
                    result = -pv;
                }
                start_period++;
            }

            for (int i = start_period; i <= end_period; i++)
            {
                if (type > 0)
                    result += CalcZw(rate, (i - 2), payment, pv, 1) - pv;
                else
                    result += CalcZw(rate, (i - 1), payment, pv, 0);
            }
            result *= rate;
            return result;
        }


        private static double CalcZw(double rate, double nper, double pmt, double pv, int type)
        {
            double Zw;
            if (rate == 0.0)
                Zw = pv + pmt*nper;
            else
            {
                double term = Math.Pow(1.0 + rate, nper);
                if (type > 0.0)
                    Zw = pv*term + pmt*(1.0 + rate)*(term - 1.0)/rate;
                else
                    Zw = pv*term + pmt*(term - 1.0)/rate;
            }
            return -Zw;
        }


        public static object CumPrinc(double rate, double nper, double pv, int start_period, int end_period,
                                      PaymentType type)
        {
            if (start_period < 1 || end_period < start_period || rate <= 0
                || end_period > nper || nper <= 0
                || pv <= 0)
                return Errors.NumError;

            double result = 0.0;
            double payment = Pmt(rate, nper, pv, 0.0, type);

            if (start_period == 1)
            {
                if (type <= 0)
                    result = payment + pv*rate;
                else
                    result = payment;

                start_period++;
            }

            for (int i = start_period++; i <= end_period; i++)
            {
                if (type > 0)
                    result += payment - (CalcZw(rate, (i - 2), payment, pv, 1) - payment)*rate;
                else
                    result += payment - CalcZw(rate, (i - 1), payment, pv, 0)*rate;
            }
            return result;

        }

        public static object Disc(DateTime settlement, DateTime maturity, double pr, double redemption,
                                  [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            double d = b.DaysInYear(settlement);
            double dsm = b.DayDifference(maturity, settlement);
            if (dsm <= 0 || d <= 0 || dsm <= 0 || redemption == 0)
                return Errors.NumError;

            return (redemption - pr)/redemption*(d/dsm);
        }

        public static object DollarDe(double decimal_value, int fraction)
        {
            if (fraction < 0)
                return Errors.NumError;
            if (fraction == 0)
                return Errors.DivError;
            bool negative = false;
            if (decimal_value < 0)
            {
                negative = true;
                decimal_value = Math.Abs(decimal_value);
            }
            var digits = 1 +   Math.Floor(Math.Log10(fraction - 0.5));
            double result = Math.Floor(decimal_value);
            result += (decimal_value - result) * Math.Pow(10, digits) / fraction;
            if (negative)
                result = 0 - result;
            return result;

        }
        public static object DollarFr(double decimal_dollar, int fraction)
        {
            if (fraction < 0)
                return Errors.NumError;
            if (fraction == 0)
                return Errors.DivError;

            bool negative = false;
            if (decimal_dollar < 0)
            {
                negative = true;
                decimal_dollar = Math.Abs(decimal_dollar);
            }
            double digits = 1 + Math.Floor(Math.Log10(fraction - 0.5));

            double result = Math.Floor(decimal_dollar);
            result += (decimal_dollar - result)*fraction/Math.Pow(10, digits);

            if (negative)
                result =  - result;
            return result;
        }

        public static double Duration(DateTime settlement, DateTime maturity, double coupon, double yld,
                                      Frequency frequency, [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            double duration = 0;
            double t, p = 0.0;
            const double f100 = 100;
            double couponCount = COUPNUM(settlement, maturity, frequency, basis);
            coupon *= f100/(double) frequency;
            yld /= (double) frequency;
            yld += 1.0;
            for (t = 1.0; t < couponCount; t++)
                duration += t*(coupon)/Math.Pow(yld, t);
            duration += couponCount*(coupon + f100)/Math.Pow(yld, couponCount);
            for (t = 1.0; t < couponCount; t++)
                p += coupon/Math.Pow(yld, t);
            p += (coupon + f100)/Math.Pow(yld, couponCount);
            duration /= p;
            duration /= (double) frequency;
            return (duration);
        }

        public static double Effect(double nominal_rate, double npery)
        {
            npery = (int) npery;
            return Math.Pow(1 + (nominal_rate/npery), npery) - 1;
        }

        public static object FVSCHEDULE(double principal, params double[]schedule)
        {
            var result = principal;
            foreach (var d in schedule)
            {
                result *= (1 + d);
            }
            return result;
        }

        public static object IntRate(DateTime settlement, DateTime maturity, double investment, double redemption,
                                     [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (investment <= 0 || redemption <= 0)
                return Errors.NumError;
            DateBasis b = DateBasis.GetBasis(basis);
            double a = b.DayDifference(maturity, settlement);
            double d = b.DaysInYear(settlement);
            if (a <= 0 || d <= 0)
                return Errors.NumError;
            return (redemption - investment)/investment*(d/a);
        }

        public static double MDURATION(DateTime settlement, DateTime maturity, double coupon, double yld,
                                       Frequency frequency, [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            double result = Duration(settlement, maturity, coupon, yld, frequency, basis);
            result /= 1.0 + (yld / (double) frequency);
            return (result);
        }

        private static object Nominal(double rate, int nper)
        {
            if (rate < 0 || nper <= 0)
                return Errors.NumError;
            return nper*(Math.Pow(1 + rate, 1.0/nper) - 1);
        }

        public static object OddFPrice(DateTime settlement, DateTime maturity, DateTime issue, DateTime first_coupon,
                                       double rate, double yld, double redemption, Frequency frequency,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (issue > settlement)
                return Errors.NumError;
            if (settlement > first_coupon)
                return Errors.NumError;
            if (first_coupon > maturity)
                return Errors.NumError;
            if (rate < 0.0 || yld < 0.0 || redemption <= 0.0)
                return Errors.NumError;
            return CalcOddFPrice(settlement, maturity, issue, first_coupon, rate, yld, redemption, frequency, basis);
        }

        private static double CalcOddFPrice(DateTime settlement, DateTime maturity, DateTime issue, DateTime first_coupon, double rate, double yld,
            double redemption, Frequency frequency, DateBasisType basis)
        {
            Contract.Ensures(!double.IsNaN(Contract.Result<double>()));
            var b = DateBasis.GetBasis(basis);
            double a = b.DaysBetween(issue, settlement);
            double ds = b.DaysBetween(settlement, first_coupon);
            double df = b.DaysBetween(issue, first_coupon);
            double e = CoupDays(settlement, maturity, frequency, basis);
            var n = (int) COUPNUM(settlement, maturity, frequency, basis);
            double scale = 100.0*rate/(int) frequency;
            double f = 1.0 + yld/(int) frequency;

            if (ds > e)
            {
                switch (basis)
                {
                    case DateBasisType.Nasd30360:
                    case DateBasisType.European360:
                        {
                            int cdays = b.DaysBetween(first_coupon, maturity);
                            n = 1 + (int) Math.Ceiling(cdays/e);
                            break;
                        }

                    default:
                        {
                            DateTime d = first_coupon;
                            for (n = 0; true; n++)
                            {
                                DateTime previousDate = d;
                                d = d.AddMonths(12/(int) frequency);
                                if ((d >= maturity))
                                {
                                    n += (int) Math.Ceiling(b.DaysBetween(previousDate, maturity)/
                                                            CoupDays(previousDate, d, frequency, basis))
                                         + 1;
                                    break;
                                }
                            }
                            a = e*DateRatio(issue, settlement, first_coupon, frequency, true, basis);
                            ds = e*DateRatio(settlement, first_coupon, first_coupon, frequency, true, basis);
                            df = e*DateRatio(issue, first_coupon, first_coupon, frequency, true, basis);
                        }
                        break;
                }
            }

            double term1 = redemption/Math.Pow(f, n - 1.0 + ds/e);
            double term2 = (df/e)/Math.Pow(f, ds/e);
            double sum = Math.Pow(f, -ds/e)*
                         (Math.Pow(f, -n) - 1/f)/(1/f - 1);

            return term1 + scale*(term2 + sum - a/e);
        }


        public static object OddFYield(DateTime settlement, DateTime maturity, DateTime issue, DateTime first_coupon,
                                       double rate, double price, double redemption, Frequency frequency,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (issue > settlement)
                return Errors.NumError;
            if (settlement > first_coupon)
            return Errors.NumError;
            if (first_coupon > maturity)
            return Errors.NumError;
            if (rate < 0.0 || price < 0.0 || redemption <= 0.0)
                return Errors.NumError;
            DoubleFunction fn = yield => CalcOddFPrice(settlement, maturity, issue, first_coupon, rate,
                                                       yield, redemption, frequency, basis) - price;
            return SolverUtils.Solve(fn, 0.00000000001, 1000, 0.00001);
        }

        public static object OddLPrice(DateTime settlement, DateTime maturity, DateTime last_interest, 
                                       double rate, double yld, double redemption, Frequency frequency,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateTime d = last_interest;
       
            do
            {
                d = d.AddMonths(12 / (int)frequency);
            } while ((d < maturity));

            var x1 = DateRatio(last_interest, settlement, d, frequency, true, basis);
            var x2 = DateRatio(last_interest, maturity, d, frequency, true, basis);
            var x3 = DateRatio(settlement, maturity, d, frequency, true, basis);

            return (redemption * (int)frequency +
                    100 * rate * (x2 - x1 * (1 + yld * x3 / (int)frequency))) /
                    (yld * x3 + (int)frequency);
        }

        public static object OddLYield(DateTime settlement, DateTime maturity, DateTime last_interest, double rate,
                                       double price, double redemption, Frequency frequency,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (settlement > maturity)
                return Errors.NumError;
            if (last_interest > settlement)
                return Errors.NumError;
            if (rate < 0.0 || price <= 0.0 || redemption <= 0.0)
                return Errors.NumError;

            DateTime d = last_interest;

            do
            {
                d = d.AddMonths(12 / (int)frequency);
            } while (d < maturity);

        
            double x1 = DateRatio(last_interest, settlement, d, frequency, true, basis );
            double x2 = DateRatio(last_interest, maturity, d, frequency, true, basis);
            double x3 = DateRatio(settlement, maturity, d, frequency, true, basis);
            double fr = (double) frequency;
            return (fr * (redemption - price) + 100 * rate * (x2 - x1)) /
                           (x3 * price + 100 * rate * x1 * x3 / fr);

        }

        private static double DateRatio(DateTime d1, DateTime d2, DateTime d3,Frequency frequency, bool endOfMonth, DateBasisType basis)
        {
            var nextCoup = ActualActualBasis.CoupCd(d1, d3, frequency, endOfMonth, true);
            var prevCoup = ActualActualBasis.CoupCd(d1, d3, frequency, endOfMonth, false);
            DateBasis b = DateBasis.GetBasis(basis);

            if (nextCoup >= d2)
                return b.DaysBetween(d1, d2)/CoupDays(prevCoup, nextCoup, frequency, basis);

            var dateRatio = b.DaysBetween(d1, nextCoup) /
                           CoupDays(prevCoup, nextCoup, frequency, basis);
            while (true)
            {
                prevCoup = nextCoup;
                nextCoup = nextCoup.AddMonths(12/(int) frequency);
                if ((nextCoup>=  d2) )
                {
                    dateRatio += b.DaysBetween(prevCoup, d2) /
                            CoupDays(prevCoup, nextCoup, frequency, basis);
                    return dateRatio;
                }
                dateRatio += 1;
            }


        }

        public static double Pmt(double rate, double nper, double pv, [Optional(0d)] double fv,
                                 [Optional(PaymentType.EndOfPeriod)] PaymentType type)
        {
            return LoanFunctions.Payment(rate, nper, pv, fv, type);
        }

        public static object PPmt(double rate, double per, double nper, double pv, [Optional(0d)] double fv,
                                  [Optional(PaymentType.EndOfPeriod)] PaymentType type)
        {
            if (per < 1 || per >= nper + 1)
                return Errors.NumError;
            return LoanFunctions.PrincipalPayment(rate, per, nper, pv, fv, type);
        }

        public static double Price(DateTime settlement, DateTime maturity, double rate, double yld, double redemption,
                                   Frequency frequency, [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (settlement > maturity)
                throw new ErrorException(Errors.NumError);
            if (rate < 0.0 || yld < 0.0 || redemption <= 0.0)
                throw new ErrorException(Errors.NumError);

            double a = CoupDayBS(settlement, maturity, frequency, basis);
            double dsc = CoupDaysNC(settlement, maturity, frequency, basis);
            double e = CoupDays(settlement, maturity, frequency, basis);
            int n = (int)COUPNUM(settlement, maturity, frequency, basis);

            double rateOverFrequency = 100.0 * rate / (double) frequency;
            double yieldOverFrequency = yld / (double) frequency;
            double exponent = dsc / e;

            if (n == 1)
                return (redemption + rateOverFrequency) / (1 + exponent * yieldOverFrequency) -
                        a / e * rateOverFrequency;

            double sum = rateOverFrequency * Math.Pow(yieldOverFrequency + 1, 1 - n - exponent) *
                         (Math.Pow(yieldOverFrequency + 1, n) - 1) / yieldOverFrequency;

            double left = redemption / Math.Pow(yieldOverFrequency + 1, n - 1.0 + dsc / e);
            double right = a / e * rateOverFrequency;

            return (left + sum - right);
        }

        public static object PriceDisc(DateTime settlement, DateTime maturity, double discount, double redemption,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            double a = b.DayDifference(maturity, settlement);
            double d = b.DaysInYear(settlement);
            if (a <= 0 || d <= 0)
                return Errors.NumError;
            return redemption - discount*redemption*a/d;
        }

        public static object PriceMat(DateTime settlement, DateTime maturity, DateTime issue, double rate, double yld,
                                      [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            DateBasis b = DateBasis.GetBasis(basis);
            double bb = b.DaysInYear(settlement);
            double dsm = b.DaysBetweenImp(settlement, maturity);
            double dim = b.DaysBetweenImp(issue, maturity);
            double a = b.DaysBetweenImp(issue, settlement);

            if (a <= 0 || bb <= 0 || dsm <= 0 || dim <= 0)
                return Errors.NumError;

            double n = 1 + (dsm/bb*yld);
            if (n == 0)
                return Errors.NumError;
            return ((100 + (dim/bb*rate*100))/(n)) - (a/bb*rate*100);
        }

        public static object Received(DateTime settlement, DateTime maturity, double investment, double discount,
                                      [Optional(0)] int basis)
        {
            if (!DateBasis.IsValidBasis(basis))
                return Errors.NumError;
            DateBasis b = DateBasis.GetBasis(basis);
            double a = b.DayDifference(maturity, settlement);
            double d = b.DaysInYear(settlement);
            if (a <= 0 || d <= 0)
                return Errors.NumError;
            double n = 1.0 - (discount*a/d);
            if (n == 0)
                return Errors.NumError;

            return investment/n;
        }

        private static object TBillEq(DateTime settlement, DateTime maturity, double discount)
        {
            double settlementd = (settlement - new DateTime(1900, 1, 1)).Days;
            double maturityd = (maturity - new DateTime(1900, 1, 1)).Days;
            double diff = maturityd - settlementd;
            if (settlementd > maturityd || discount < 0 || diff > 365)
                return Errors.NumError;
            double divisor = 360 - discount*diff;
            if (divisor == 0)
                return Errors.NumError;
            return (365*discount)/divisor;
        }


        
        public static object XIrr(IArray values, IArray dates, double guess)
        {
                        if (values.RowCount != dates.RowCount)
                return Errors.ValueError;
            if (values.ColumnCount != dates.ColumnCount)
                return Errors.ValueError;
List<double> values2;
            List<DateTime> dates2;
            ExtractValuesAndDates(values, dates, out values2, out dates2);

            DoubleFunction f = x => GetXNpv(x, values2, dates2);
            return SolverUtils.Solve(f, -1, 1000, 0.0000001);
        }

        
        public static object XNpv(double rate, IArray values, IArray dates)
        {
            if (values.RowCount != dates.RowCount)
                return Errors.ValueError;
            if (values.ColumnCount != dates.ColumnCount)
                return Errors.ValueError;
            List<double> values2;
            List<DateTime> dates2;
            ExtractValuesAndDates(values, dates, out values2, out dates2);
            return GetXNpv(rate, values2, dates2);
        }

        private static double GetXNpv(double rate, List<double> values2, List<DateTime> dates2)
        {
            double result = 0;
            for (int i = 0; i < values2.Count; i++)
            {
                result += values2[i]/
                          (Math.Pow(1 + rate,
                                    ((DateTime) dates2[i] - (DateTime) dates2[0]).TotalDays/365.0));
            }
            return result;
        }

        private static void ExtractValuesAndDates(IArray values, IArray dates, out List<double> values2, out List<DateTime> dates2)
        {
            values2 = new List<double>();
            dates2 = new List<DateTime>();
            for (int row = 0; row < values.RowCount; row++)
            {
                for (int column = 0; column < values.ColumnCount; column++)
                {
                    values2.Add( (double) values[row, column]);
                    dates2.Add( (DateTime)dates[row, column]);
                }
            }
        }

        public static object Yield(DateTime settlement, DateTime maturity, double rate, double par, double redemption,
                                   Frequency frequency, [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (rate < 0.0 || par < 0.0 || redemption <= 0.0)
                throw new ErrorException(Errors.NumError);

            double n = COUPNUM(settlement, maturity, frequency, basis);
            if (n <= 1.0)
            {
                double a = CoupDayBS(settlement, maturity, frequency, basis);
                double d = CoupDaysNC(settlement, maturity, frequency,basis);
                double e = CoupDays(settlement, maturity, frequency, basis);

                double coeff = (double)frequency*e/d;
                double num = (redemption/100.0 + rate/(double)frequency)
                             - (par/100.0 + (a/e*
                                                   rate/(double)frequency));
                double den = par/100.0 + (a/e*rate/
                                                (double)frequency);

                return (num/den*coeff);
            }
            DoubleFunction f = x => Price(settlement, maturity, rate, x, redemption, frequency, basis) - par;

            double result = SolverUtils.Solve(f, 0.000000001, 1000, 0.0000001);
            return result;
        }

        public static object YieldDisc(DateTime settlement, DateTime maturity, double pr, double redemption,
                                       [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (redemption <= 00 || pr < 0)
                return Errors.NumError;
            if (settlement >= maturity || pr < 0 || redemption < 0)
                return Errors.NumError;
            double rate = redemption/pr - 1;
            var yfrac = DateFunctions.YearFrac(settlement, maturity, basis);
            return rate/yfrac;
        }

        public static object YieldMat(DateTime settlement, DateTime maturity, DateTime issue, double rate, double pr,
                                      [Optional(DateBasisType.Nasd30360)] DateBasisType basis)
        {
            if (rate < 0)
                return Errors.NumError;
            if (settlement >= maturity)
                return Errors.NumError;
            DateBasis b = DateBasis.GetBasis(basis);
            double issueMaturity = b.YearDifference(maturity, issue);
            double issueSettlement = b.YearDifference(settlement, issue);
            double settlementMaturity = b.YearDifference(maturity, settlement);
            double result = 1.0 + issueMaturity*rate;
            result = result/((pr/100) + (issueSettlement*rate));
            result--;
            result = result/settlementMaturity;
            return result;
        }
    }
}